/*
Copyright 2020 The Kruise Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
	appspub "github.com/openkruise/kruise/apis/apps/pub"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
)

const (
	// CloneSetInstanceID is a unique id for Pods and PVCs.
	// Each pod and the pvcs it owns have the same instance-id.
	CloneSetInstanceID = "apps.kruise.io/cloneset-instance-id"

	// DefaultCloneSetMaxUnavailable is the default value of maxUnavailable for CloneSet update strategy.
	DefaultCloneSetMaxUnavailable = "20%"

	// CloneSetScalingExcludePreparingDeleteKey is the label key that enables scalingExcludePreparingDelete
	// only for this CloneSet, which means it will calculate scale number excluding Pods in PreparingDelete state.
	CloneSetScalingExcludePreparingDeleteKey = "apps.kruise.io/cloneset-scaling-exclude-preparing-delete"
)

// CloneSetSpec defines the desired state of CloneSet
type CloneSetSpec struct {
	// Replicas is the desired number of replicas of the given Template.
	// These are replicas in the sense that they are instantiations of the
	// same Template.
	// If unspecified, defaults to 1.
	Replicas *int32 `json:"replicas,omitempty"`

	// Selector is a label query over pods that should match the replica count.
	// It must match the pod template's labels.
	// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
	Selector *metav1.LabelSelector `json:"selector"`

	// Template describes the pods that will be created.
	// +kubebuilder:pruning:PreserveUnknownFields
	// +kubebuilder:validation:Schemaless
	Template v1.PodTemplateSpec `json:"template"`

	// VolumeClaimTemplates is a list of claims that pods are allowed to reference.
	// Note that PVC will be deleted when its pod has been deleted.
	// +kubebuilder:pruning:PreserveUnknownFields
	// +kubebuilder:validation:Schemaless
	VolumeClaimTemplates []v1.PersistentVolumeClaim `json:"volumeClaimTemplates,omitempty"`

	// ScaleStrategy indicates the ScaleStrategy that will be employed to create and delete Pods in the CloneSet.
	// ScaleStrategy 表示将用于在 CloneSet 中创建和删除 Pod 的 ScaleStrategy。
	ScaleStrategy CloneSetScaleStrategy `json:"scaleStrategy,omitempty"`

	// UpdateStrategy indicates the UpdateStrategy that will be employed to update Pods in the CloneSet when a revision is made to Template.
	// UpdateStrategy 表示在对 Template 进行修订时，将采用的 UpdateStrategy 来更新 CloneSet 中的 Pod。
	UpdateStrategy CloneSetUpdateStrategy `json:"updateStrategy,omitempty"`

	// RevisionHistoryLimit is the maximum number of revisions that will
	// be maintained in the CloneSet's revision history. The revision history
	// consists of all revisions not represented by a currently applied
	// CloneSetSpec version. The default value is 10.
	// RevisionHistoryLimit 是 CloneSet 修订历史中将保留的最大修订数量。修订历史包括当前应用的 CloneSetSpec 版本未代表的所有修订。默认值为 10。
	RevisionHistoryLimit *int32 `json:"revisionHistoryLimit,omitempty"`

	// Minimum number of seconds for which a newly created pod should be ready
	// without any of its container crashing, for it to be considered available.
	// Defaults to 0 (pod will be considered available as soon as it is ready)
	// MinReadySeconds 表示新创建的 pod 应准备就绪的最短秒数
	// 且其容器不会崩溃，这样它才被视为可用。
	// 默认为 0（pod 准备就绪后将立即被视为可用）
	MinReadySeconds int32 `json:"minReadySeconds,omitempty"`

	// Lifecycle defines the lifecycle hooks for Pods pre-available(pre-normal), pre-delete, in-place update.
	// Lifecycle 定义了 Pods 预可用（预正常）、预删除、原地更新的生命周期钩子。
	Lifecycle *appspub.Lifecycle `json:"lifecycle,omitempty"`
}

// CloneSetScaleStrategy defines strategies for pods scale.
// CloneSetScaleStrategy 定义了 pod 扩展的策略。
type CloneSetScaleStrategy struct {
	// PodsToDelete is the names of Pod should be deleted.
	// Note that this list will be truncated for non-existing pod names.
	// PodsToDelete 是应该删除的 Pod 的名称。
	// 请注意，如果 Pod 名称不存在，此列表将被截断。
	PodsToDelete []string `json:"podsToDelete,omitempty"`

	// The maximum number of pods that can be unavailable for scaled pods.
	// This field can control the changes rate of replicas for CloneSet so as to minimize the impact for users' service.
	// The scale will fail if the number of unavailable pods were greater than this MaxUnavailable at scaling up.
	// MaxUnavailable works only when scaling up.
	// 扩容后 Pod 最大可用 Pod 数。
	// 该字段可以控制 CloneSet 副本的变化率，以尽量减少对用户服务的影响。
	// 扩容时，如果不可用 Pod 数大于此 MaxUnavailable，则扩容失败。
	// MaxUnavailable 仅在扩容时有效。
	MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty"`

	// Indicate if cloneSet will reuse already existed pvc to
	// rebuild a new pod
	// 指示 cloneSet 是否将重用已存在的 pvc 来重建一个新的 pod
	DisablePVCReuse bool `json:"disablePVCReuse,omitempty"`
}

// CloneSetUpdateStrategy 定义了 pod 更新的策略。
type CloneSetUpdateStrategy struct {
	// Type indicates the type of the CloneSetUpdateStrategy.
	// Default is ReCreate.
	// Type 表示 CloneSetUpdateStrategy 的类型，
	// 共有 ReCreate(默认)、InPlaceIfPossible、InPlaceOnly 三种类型
	Type CloneSetUpdateStrategyType `json:"type,omitempty"`

	// Partition is the desired number of pods in old revisions.
	// Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
	// Absolute number is calculated from percentage by rounding up by default.
	// It means when partition is set during pods updating, (replicas - partition value) number of pods will be updated.
	// Default value is 0.
	// Partition 是旧版本中所需的 Pod 数量。
	// 值可以是绝对数字（例如：5）或所需 Pod 的百分比（例如：10％）。
	// 默认情况下，绝对数字是通过向上舍入从百分比计算得出的。
	// 这意味着当在 Pod 更新期间设置分区时，将更新（Replicas - Partition）个 Pod。在 CloneSetStatus.ExpectedUpdatedReplicas 会展示此值
	// 默认值为 0。
	Partition *intstr.IntOrString `json:"partition,omitempty"`

	// The maximum number of pods that can be unavailable during update or scale.
	// Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
	// Absolute number is calculated from percentage by rounding up by default.
	// When maxSurge > 0, absolute number is calculated from percentage by rounding down.
	// Defaults to 20%.
	// MaxUnavailable 表示更新或扩展期间不可用的 Pod 的最大数量。
	// 值可以是绝对数字（例如：5）或所需 Pod 的百分比（例如：10％）。
	// 默认情况下，绝对数字是通过向上舍入从百分比计算得出的。
	// 当 maxSurge > 0 时，绝对数字是通过向下舍入从百分比计算得出的。
	// 默认为 20％。
	MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty"`

	// The maximum number of pods that can be scheduled above the desired replicas during update or specified delete.
	// Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
	// Absolute number is calculated from percentage by rounding up.
	// Defaults to 0.
	// MaxSurge 表示在更新或指定删除期间，可以在所需副本之上调度的最大 Pod 数量。
	// 值可以是绝对数字（例如：5）或所需 Pod 的百分比（例如：10％）。
	// 绝对数字是通过向上舍入百分比计算得出的。
	// 默认为 0。
	MaxSurge *intstr.IntOrString `json:"maxSurge,omitempty"`

	// Paused indicates that the CloneSet is paused.
	// Default value is false
	// Paused 表示 CloneSet 已暂停。
	// 默认值为 false
	Paused bool `json:"paused,omitempty"`

	// Priorities are the rules for calculating the priority of updating pods.
	// Each pod to be updated, will pass through these terms and get a sum of weights.
	// Priorities 是计算更新 pod 优先级的规则。
	// 每个要更新的 pod 都会经过这些条件，得到一个权重总和。
	PriorityStrategy *appspub.UpdatePriorityStrategy `json:"priorityStrategy,omitempty"`

	// ScatterStrategy defines the scatter rules to make pods been scattered when update.
	// This will avoid pods with the same key-value to be updated in one batch.
	// - Note that pods will be scattered after priority sort. So, although priority strategy and scatter strategy can be applied together, we suggest to use either one of them.
	// - If scatterStrategy is used, we suggest to just use one term. Otherwise, the update order can be hard to understand.
	// ScatterStrategy 定义了 pod 更新时分散的规则。
	// 这样可以避免将具有相同 key-value 的 pod 放在一个批次中更新。
	// - 注意，优先级排序后 pod 会被分散。因此，虽然优先级策略和分散策略可以一起使用，但我们建议使用其中任何一种。
	// - 如果使用 scatterStrategy，我们建议只使用一个术语。否则，更新顺序可能难以理解。
	ScatterStrategy UpdateScatterStrategy `json:"scatterStrategy,omitempty"`

	// InPlaceUpdateStrategy contains strategies for in-place update.
	// InPlaceUpdateStrategy 包含原地更新的策略。
	InPlaceUpdateStrategy *appspub.InPlaceUpdateStrategy `json:"inPlaceUpdateStrategy,omitempty"`
}

// CloneSetUpdateStrategyType defines strategies for pods in-place update.
type CloneSetUpdateStrategyType string

const (
	// RecreateCloneSetUpdateStrategyType indicates that we always delete Pod and create new Pod
	// during Pod update, which is the default behavior.
	// RecreateCloneSetUpdateStrategyType 表示我们在 Pod 更新期间始终删除 Pod 并创建新的 Pod，这是默认行为。
	RecreateCloneSetUpdateStrategyType CloneSetUpdateStrategyType = "ReCreate"

	// InPlaceIfPossibleCloneSetUpdateStrategyType indicates that we try to in-place update Pod instead of
	// recreating Pod when possible. Currently, only image update of pod spec is allowed. Any other changes to the pod
	// spec will fall back to ReCreate CloneSetUpdateStrategyType where pod will be recreated.
	// InPlaceIfPossibleCloneSetUpdateStrategyType 表示我们尽量尝试原地更新 Pod，而不是重新创建 Pod。
	// 目前，只允许更新 Pod 规范的镜像。对 Pod 规范的任何其他更改都将回退到 ReCreate，也就是重新创建 Pod。
	InPlaceIfPossibleCloneSetUpdateStrategyType CloneSetUpdateStrategyType = "InPlaceIfPossible"

	// InPlaceOnlyCloneSetUpdateStrategyType indicates that we will in-place update Pod instead of
	// recreating pod. Currently we only allow image update for pod spec. Any other changes to the pod spec will be
	// rejected by kube-apiserver
	// InPlaceOnlyCloneSetUpdateStrategyType 表示我们将原地更新 Pod，而不是重新创建 Pod。
	// 目前我们仅允许对 Pod 规范进行镜像更新。对 Pod 规范的任何其他更改都将被 kube-apiserver 拒绝
	InPlaceOnlyCloneSetUpdateStrategyType CloneSetUpdateStrategyType = "InPlaceOnly"
)

// CloneSetStatus defines the observed state of CloneSet
// CloneSetStatus 定义 CloneSet 的观察状态
type CloneSetStatus struct {
	// ObservedGeneration is the most recent generation observed for this CloneSet. It corresponds to the
	// CloneSet's generation, which is updated on mutation by the API Server.
	// ObservedGeneration 是针对此 CloneSet 观察到的最新一代。
	// 它对应于 CloneSet 的 generation 字段，generation 字段由 API 服务器在发生变更时自动更新（加一操作）。
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`

	// Replicas is the number of Pods created by the CloneSet controller.
	// Replicas 是 CloneSet 控制器创建的 Pod 数量。
	Replicas int32 `json:"replicas"`

	// ReadyReplicas is the number of Pods created by the CloneSet controller that have a Ready Condition.
	// ReadyReplicas 是 CloneSet 控制器创建的具有就绪条件的 Pod 数量。
	ReadyReplicas int32 `json:"readyReplicas"`

	// AvailableReplicas is the number of Pods created by the CloneSet controller that have a Ready Condition for at least minReadySeconds.
	// AvailableReplicas 是 CloneSet 控制器创建的处于就绪状态至少持续 minReadySeconds 的 Pod 数量，minReadySeconds 在 CloneSetSpec 中指定。
	AvailableReplicas int32 `json:"availableReplicas"`

	// UpdatedReplicas is the number of Pods created by the CloneSet controller from the CloneSet version indicated by updateRevision.
	// UpdatedReplicas 是 CloneSet 控制器根据 UpdateRevision 指示的 CloneSet 版本创建的 Pod 数量。
	UpdatedReplicas int32 `json:"updatedReplicas"`

	// UpdatedReadyReplicas is the number of Pods created by the CloneSet controller from the CloneSet version
	// indicated by updateRevision and have a Ready Condition.
	// UpdatedReadyReplicas 是 CloneSet 控制器从 UpdateRevision 指示的 CloneSet 版本创建的具有就绪状态的 Pod 数量。
	UpdatedReadyReplicas int32 `json:"updatedReadyReplicas"`

	// UpdatedAvailableReplicas is the number of Pods created by the CloneSet controller from the CloneSet version
	// indicated by updateRevision and have a Ready Condition for at least minReadySeconds.
	// UpdatedAvailableReplicas 是 CloneSet 控制器从 UpdateRevision 指示的 CloneSet 版本创建的处于就绪状态至少持续 minReadySeconds 的 Pod 数量
	UpdatedAvailableReplicas int32 `json:"updatedAvailableReplicas,omitempty"`

	// ExpectedUpdatedReplicas is the number of Pods that should be updated by CloneSet controller.
	// This field is calculated via Replicas - Partition.
	// ExpectedUpdatedReplicas 是 CloneSet 控制器期望更新的 Pod 数量。
	// 此字段通过 Replicas - Partition 计算得出。
	ExpectedUpdatedReplicas int32 `json:"expectedUpdatedReplicas,omitempty"`

	// UpdateRevision, if not empty, indicates the latest revision of the CloneSet.
	// UpdateRevision，如果不为空，则表示CloneSet的最新修订版本。
	UpdateRevision string `json:"updateRevision,omitempty"`

	// currentRevision, if not empty, indicates the current revision version of the CloneSet.
	CurrentRevision string `json:"currentRevision,omitempty"`

	// CollisionCount is the count of hash collisions for the CloneSet. The CloneSet controller
	// uses this field as a collision avoidance mechanism when it needs to create the name for the
	// newest ControllerRevision.
	CollisionCount *int32 `json:"collisionCount,omitempty"`

	// Conditions represents the latest available observations of a CloneSet's current state.
	Conditions []CloneSetCondition `json:"conditions,omitempty"`

	// LabelSelector is label selectors for query over pods that should match the replica count used by HPA.
	LabelSelector string `json:"labelSelector,omitempty"`
}

// CloneSetConditionType is type for CloneSet conditions.
type CloneSetConditionType string

const (
	// CloneSetConditionFailedScale indicates cloneset controller failed to create or delete pods/pvc.
	CloneSetConditionFailedScale CloneSetConditionType = "FailedScale"
	// CloneSetConditionFailedUpdate indicates cloneset controller failed to update pods.
	CloneSetConditionFailedUpdate CloneSetConditionType = "FailedUpdate"
)

// CloneSetCondition describes the state of a CloneSet at a certain point.
type CloneSetCondition struct {
	// Type of CloneSet condition.
	Type CloneSetConditionType `json:"type"`
	// Status of the condition, one of True, False, Unknown.
	Status v1.ConditionStatus `json:"status"`
	// Last time the condition transitioned from one status to another.
	LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
	// The reason for the condition's last transition.
	Reason string `json:"reason,omitempty"`
	// A human readable message indicating details about the transition.
	Message string `json:"message,omitempty"`
}

// +genclient
// +genclient:method=GetScale,verb=get,subresource=scale,result=k8s.io/api/autoscaling/v1.Scale
// +genclient:method=UpdateScale,verb=update,subresource=scale,input=k8s.io/api/autoscaling/v1.Scale,result=k8s.io/api/autoscaling/v1.Scale
// +k8s:openapi-gen=true
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:subresource:scale:specpath=.spec.replicas,statuspath=.status.replicas,selectorpath=.status.labelSelector
// +kubebuilder:resource:shortName=clone
// +kubebuilder:printcolumn:name="DESIRED",type="integer",JSONPath=".spec.replicas",description="The desired number of pods."
// +kubebuilder:printcolumn:name="UPDATED",type="integer",JSONPath=".status.updatedReplicas",description="The number of pods updated."
// +kubebuilder:printcolumn:name="UPDATED_READY",type="integer",JSONPath=".status.updatedReadyReplicas",description="The number of pods updated and ready."
// +kubebuilder:printcolumn:name="READY",type="integer",JSONPath=".status.readyReplicas",description="The number of pods ready."
// +kubebuilder:printcolumn:name="TOTAL",type="integer",JSONPath=".status.replicas",description="The number of currently all pods."
// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp",description="CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC."
// +kubebuilder:printcolumn:name="CONTAINERS",type="string",priority=1,JSONPath=".spec.template.spec.containers[*].name",description="The containers of currently cloneset."
// +kubebuilder:printcolumn:name="IMAGES",type="string",priority=1,JSONPath=".spec.template.spec.containers[*].image",description="The images of currently cloneset."
// +kubebuilder:printcolumn:name="SELECTOR",type="string",priority=1,JSONPath=".status.labelSelector",description="The selector of currently cloneset."

// CloneSet is the Schema for the clonesets API
type CloneSet struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   CloneSetSpec   `json:"spec,omitempty"`
	Status CloneSetStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// CloneSetList contains a list of CloneSet
type CloneSetList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []CloneSet `json:"items"`
}

func init() {
	SchemeBuilder.Register(&CloneSet{}, &CloneSetList{})
}
